package publisher.eventadmin;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import library.service.Book;
import library.service.LibraryService;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;

import com.prosyst.util.ref.Log;

public class Activator implements BundleActivator, ServiceListener, Runnable {

	private static final String OR_OPERATOR = "|";
	private static final String SUCCESS_EVENT_TOPIC = "libraryservice/search/event/success";
	private static final String FAIL_EVENT_TOPIC = "libraryservice/search/event/fail";

	private BundleContext bundleContext;

	// eventAdmin service stuff
	private EventAdmin eventAdmin;
	private ServiceReference eventAdminServiceRef;
	private Thread eventGenerator;

	// Library service
	private String TECHNICAL_BOOKS_FILTER = "(category=technical)";
	private LibraryService libraryService;
	private ServiceReference libraryServiceRef;

	// logging stuff
	private Log log;

	// database simulator
	private String[] bookNamesRepository = new String[] { "Design Patterns",
			"UML", "Effective Java", "Java", "C#", "Ruby" };

	// --- BundleActivator interface -------------------------------------------
	public void start(BundleContext bundleContext) throws Exception {
		this.bundleContext = bundleContext;

		// initialize log
		initializeLog();

		// TODO: ask Sinisha for this
		ServiceReference eventAdminServiceRef = bundleContext
				.getServiceReference(EventAdmin.class.getName());
		if (eventAdminServiceRef != null) {
			getEventAdminService(eventAdminServiceRef);
		}

		ServiceReference libraryServiceRef = bundleContext
				.getServiceReference(LibraryService.class.getName());
		if (libraryServiceRef != null) {
			getLibraryService(libraryServiceRef);
		}

		// register serviceListener
		bundleContext.addServiceListener(this, getServiceListenerFilter());

	}

	public void stop(BundleContext bundleContext) throws Exception {

		// 1. LibraryService cleanup
		ungetLibraryService();

		// 2. EventAdmin clean up
		ungetEventAdminService();

		// 3. log clean up
		log.close();
		log = null;

		// 4. serviceListener clean up
		bundleContext.removeServiceListener(this);
	}

	// --- Auxiliary methods ---------------------------------------------------
	private void initializeLog() {
		log = new Log(bundleContext);
		log.setLogLevel(Log.INFO);
		log.setPrintLevel(Log.INFO);
	}

	private void getLibraryService(ServiceReference serviceRef) {
		log.info("getLibraryService()");
		ServiceReference[] serviceRefs = null;
		try {
			serviceRefs = bundleContext.getAllServiceReferences(
					LibraryService.class.getName(), TECHNICAL_BOOKS_FILTER);
		} catch (InvalidSyntaxException e) {
			e.printStackTrace();
		}
		if (serviceRefs != null) {
			libraryServiceRef = serviceRefs[0];
			if (libraryServiceRef.equals(serviceRef)) {
				libraryService = (LibraryService) bundleContext
						.getService(libraryServiceRef);
			}

			// release serviceRefs-array and hold only libraryServiceRef
			serviceRefs = null;
		}

	}

	private synchronized void ungetLibraryService() {
		log.info("ungetLibraryService()");
		if (libraryService != null) {
			bundleContext.ungetService(libraryServiceRef);
			libraryService = null;
			libraryServiceRef = null;
		}

	}

	private void getEventAdminService(ServiceReference serviceRef) {
		log.info("getEventAdminService()");
		eventAdmin = (EventAdmin) bundleContext.getService(serviceRef);
		if (eventAdmin != null) {
			eventAdminServiceRef = serviceRef;

			// create and run new Thread which will execute run() method
			eventGenerator = new Thread(this, "EventAdmin generator thread");
			eventGenerator.start();
		}
	}

	private synchronized void ungetEventAdminService() {
		log.info("ungetEventAdminService()");

		if (eventAdmin != null) {
			bundleContext.ungetService(eventAdminServiceRef);
			eventAdmin = null;
			eventAdminServiceRef = null;
		}
	}

	// --- Runnable interface --------------------------------------------------
	public void run() {
		int counter = 0;

		synchronized (this) {
			while (eventAdmin != null && libraryService != null) {
				String bookName = getRandomBookName();
				Book book = libraryService.findBookByName(bookName);

				Map eventPropertyMap = new HashMap();
				eventPropertyMap.put("COMMON_COUNTER", new Integer(counter));
				

				if (book != null) {
					eventPropertyMap.put("BOOK_NAME", book.getName());
					Event event = new Event(SUCCESS_EVENT_TOPIC,
							eventPropertyMap);
					eventAdmin.sendEvent(event);

					log.info("PUBLISHER: New SUCCESS_EVENT sent!");
				} else {
					eventPropertyMap.put("BOOK_NAME", bookName);
					Event event = new Event(FAIL_EVENT_TOPIC, eventPropertyMap);
					eventAdmin.sendEvent(event);

					log.info("PUBLISHER: New FAIL_EVENT sent!");
				}
				counter++;
				try {
					wait(5000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

	}

	private String getRandomBookName() {
		int randomArrayIndex = (int) (Math.random() * (bookNamesRepository.length));
		log.info("PUBLISHER: Random chosen book: "
				+ bookNamesRepository[randomArrayIndex]);
		return bookNamesRepository[randomArrayIndex];
	}

	// --- ServiceListener interface -------------------------------------------
	public void serviceChanged(ServiceEvent serviceEvent) {
		log.info("serviceChanged()");
		if (serviceEvent == null || serviceEvent.getServiceReference() == null) {
			throw new IllegalArgumentException("Illegal ServiceEvent Argument");
		}

		ServiceReference serviceRef = serviceEvent.getServiceReference();
		Object service = bundleContext.getService(serviceRef);

		// LibraryService
		if (service instanceof LibraryService) {
			switch (serviceEvent.getType()) {
			case ServiceEvent.REGISTERED: {
				// load LibraryService stuff
				if (libraryServiceRef == null) {
					log.info("LibraryService.REGISTERED");
					getLibraryService(serviceRef);
				}
				break;
			}
			case ServiceEvent.UNREGISTERING: {
				// clean up libraryService stuff
				log.info("LibraryService.UNREGISTERING");
				ungetLibraryService();
				break;
			}
			}
		}

		// EventAdmin
		if (service instanceof EventAdmin) {
			switch (serviceEvent.getType()) {
			case ServiceEvent.REGISTERED: {
				// load EventAdmin stuff
				if (eventAdminServiceRef == null) {
					log.info("EventAdmin.REGISTERED");
					getEventAdminService(serviceRef);
				}
				break;
			}
			case ServiceEvent.UNREGISTERING: {
				// clean up libraryService stuff
				log.info("EventAdmin.UNREGISTERING");
				ungetEventAdminService();
				break;
			}

			}
		}
	}

	// --- Filter Stuff --------------------------------------------------------
	private String getServiceListenerFilter() {
		String libraryServiceFilter = getSingleFilter(Constants.OBJECTCLASS,
				LibraryService.class.getName());
		String eventAdminFilter = getSingleFilter(Constants.OBJECTCLASS,
				EventAdmin.class.getName());

		Collection filterList = new ArrayList();
		filterList.add(libraryServiceFilter);
		filterList.add(eventAdminFilter);

		return getHomogeneousComplexFilter(OR_OPERATOR, filterList);
	}

	private String getSingleFilter(String key, String value) {
		StringBuffer buffer = new StringBuffer(20);
		buffer.append("(").append(key).append("=").append(value).append(")");

		log.info(buffer.toString());
		return buffer.toString();
	}

	private String getHomogeneousComplexFilter(String operation,
			Collection collection) {
		StringBuffer buffer = new StringBuffer(20);
		buffer.append("(");
		buffer.append(operation);

		for (Iterator iterator = collection.iterator(); iterator.hasNext();) {
			buffer.append(iterator.next());
		}

		buffer.append(")");

		log.info(buffer.toString());
		return buffer.toString();
	}
}
